package com.beesphere.excel.writer.impls;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.beesphere.excel.lang.LangUtils;
import com.beesphere.excel.reader.CellType;
import com.beesphere.excel.writer.ExcelWriter;
import com.beesphere.excel.writer.ExcelWriterException;
import com.beesphere.excel.writer.LineCell;
import com.beesphere.excel.writer.style.CellStyleRegistry;

public abstract class AbstractExcelWriter extends DefaultHandler implements ExcelWriter {
	
	private static final SAXParserFactory DEFAULT_FACTORY = SAXParserFactory.newInstance();
	protected Charset charset = LangUtils.DEFAULT_CHARSET;
	
	protected transient SAXParserFactory factory;
	protected transient SAXParser saxParser;
	
	protected Workbook wb;
	private Sheet sheet;
	private Row row;
	private Cell cell;
	private CreationHelper creationHelper;
	
	private LineCell lineCell;
	
	private String currentTag;
	private boolean sheetAsRoot = true;
	
	private int rowsCount;
	private short cellsCount;
	private boolean cellValueSet;
	private static final Map<CellType, Integer> TYPES = new HashMap<CellType, Integer> ();
	static {
		TYPES.put (CellType.STRING, Cell.CELL_TYPE_STRING);
		TYPES.put (CellType.BOOLEAN, Cell.CELL_TYPE_BOOLEAN);
		TYPES.put (CellType.DATE, Cell.CELL_TYPE_NUMERIC);
		TYPES.put (CellType.DECIMAL, Cell.CELL_TYPE_NUMERIC);
		TYPES.put (CellType.ERROR, Cell.CELL_TYPE_ERROR);
		TYPES.put (CellType.FORMULA, Cell.CELL_TYPE_FORMULA);
		TYPES.put (CellType.INTEGER, Cell.CELL_TYPE_NUMERIC);
	}
	
	protected boolean ooxml;
	protected boolean installCustomFormats;
	protected Map<String, String> tags = new HashMap<String, String> ();
	protected CellStyleRegistry styleRegistry;

	public AbstractExcelWriter (String saxParserClassFactory) throws ExcelWriterException {
		super ();
		try {
			if (saxParserClassFactory != null) {
				factory = (SAXParserFactory)Thread.currentThread().getContextClassLoader().loadClass (saxParserClassFactory).newInstance ();
			} else {
				factory = DEFAULT_FACTORY;
			}
			factory.setNamespaceAware (true);
			saxParser = factory.newSAXParser ();
		} catch (Exception e) {
			throw new ExcelWriterException (e);
		} 
	}
	
	public AbstractExcelWriter () throws ExcelWriterException {
		this (null);
	}

	protected abstract void visitStart () throws ExcelWriterException;
	protected abstract void visitEnd () throws ExcelWriterException;
	protected abstract void visitStartSheet (int index) throws ExcelWriterException;
	protected abstract void visitEndSheet () throws ExcelWriterException;
	protected abstract void visitStartLine (int index) throws ExcelWriterException;
	protected abstract void visitEndLine () throws ExcelWriterException;
	protected abstract void visitCell (LineCell cell, int index) throws ExcelWriterException;

	@Override
	public void reset () {
		saxParser.reset ();
		wb = null;
		sheet = null;
		row = null;
		cell = null;
		creationHelper = null;
		lineCell = null;
		currentTag = null;
		rowsCount = 0;
		cellsCount = 0;
		cellValueSet = false;
		ooxml = false;
	}

	@Override
	public void write (InputStream input) throws ExcelWriterException {
		if (tags == null) {
			tags = new HashMap<String, String> ();
		}
		try {
			if (charset != null) {
				InputStreamReader reader = new InputStreamReader (input, charset);
				InputSource source = new InputSource (reader);
				saxParser.parse(source, this);
			} else {
				saxParser.parse (input, this);
			}
		} catch (SAXException sex) {
			if (sex.getCause() instanceof ExcelWriterException) {
				throw (ExcelWriterException)sex.getCause ();
			}
			throw new ExcelWriterException (sex);
		} catch (Throwable th) {
			throw new ExcelWriterException (th);
		} 
	}
	
	@Override
	public void startDocument () throws SAXException {
		wb = ooxml ? new XSSFWorkbook () : new HSSFWorkbook ();
		creationHelper = wb.getCreationHelper();
		lineCell = new MapLineCell ();
	}
	
	@Override
	public void startElement(String uri, String localName, String name,
			Attributes attributes) throws SAXException {
		String eName = localName; // element name
		if (LangUtils.EMPTY.equals(eName)) {
			eName = name; // namespaceAware = false
		}
		currentTag = eName;
		try {
			if (eName.equals (getTag (LangUtils.ROOT))) {
				visitStart ();
				sheetAsRoot = false;
			} else if (currentTag.equals (getTag (LangUtils.SHEET))) {
				sheet = wb.createSheet ();
				rowsCount = 0;
				cellsCount = 0;
				visitStartSheet (wb.getSheetIndex (sheet));
			} else if (isLine(eName)) {
				row = sheet.createRow (rowsCount++);
				cellsCount = 0;
				visitStartLine(rowsCount - 1);
			} else if (isCell(eName)) {
				cell = row.createCell (cellsCount++);
				cellValueSet = false;
			}
		} catch (ExcelWriterException e) {
			throw new SAXException (e);
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		if (currentTag == null) {
			return;
		}
		if ((currentTag.startsWith(LangUtils.H_) || currentTag.startsWith(LangUtils.C_)) && !cellValueSet) {
			cellValueSet = true;
			Object value = null;
			try {
				value = new String (ch, start, length);
			} catch (Throwable th) {
				// Ignore
			}
			if (value == null) {
				return;
			}
			try {
				lineCell.addInfo (LineCell.VALUE, value);
				visitCell (lineCell, cellsCount - 1);
				value = lineCell.getInfo (LineCell.VALUE);
				
				CellType type = (CellType) lineCell.getInfo(LineCell.TYPE);
				if (type == null) {
					type = CellType.STRING;
				}
				Integer cellType = TYPES.get (type);
				if (cellType == null) {
					cellType = Cell.CELL_TYPE_STRING;
				}
				// Set Type
				cell.setCellType (cellType);
				
				// Set Value
				switch (cellType) {
					case Cell.CELL_TYPE_BOOLEAN:
						cell.setCellValue ((Boolean)value);
						break;
					case Cell.CELL_TYPE_ERROR:
						cell.setCellValue ((Byte)value);
						break;	
					case Cell.CELL_TYPE_FORMULA:
						cell.setCellValue ((String)value);
						break;	
					case Cell.CELL_TYPE_NUMERIC:
						if (CellType.DATE.equals (type)) {
							cell.setCellValue ((Date)value);
						} else if (CellType.DECIMAL.equals (type)) {
							cell.setCellValue ((Double)value);
						} else if (CellType.INTEGER.equals (type)) {
							cell.setCellValue ((Integer)value);
						}
						break;
					case Cell.CELL_TYPE_STRING:
						cell.setCellValue (creationHelper.createRichTextString ((String)value));
						break;	
					default:
						cell.setCellValue (creationHelper.createRichTextString ((String)value));
						break;
				}
				
				// Set Style
				if (styleRegistry != null) {
					String styleName = (String)lineCell.getInfo (LineCell.STYLE);
					if (styleName != null) {
						CellStyle style = styleRegistry.create (styleName, new WorkbookContext (wb, installCustomFormats));
						if (style != null) {
							cell.setCellStyle (style);
						}
					}
				}
				
			} catch (ExcelWriterException e) {
				throw new SAXException (e);
			}
		}
	}

	@Override
	public void endElement (String uri, String localName, String name)
		throws SAXException {
		String eName = localName; // element name
		if (LangUtils.EMPTY.equals(eName)) {
			eName = name; // namespaceAware = false
		}
		try {
			if (eName.equals (getTag (LangUtils.ROOT))) {
				visitEnd();
			} else if (eName.equals(getTag (LangUtils.SHEET))) {
				visitEndSheet ();
				if (sheetAsRoot) {
					visitEnd();
				}
			} else if (isLine(eName)) {
				visitEndLine();
			} else if (isCell(eName)) {
				lineCell.renew ();
			}
		} catch (ExcelWriterException e) {
			throw new SAXException (e);
		}
	}
	
	@Override
	public void endDocument () throws SAXException {
	}

	public void setValidating (boolean validating) {
		factory.setValidating (validating);
	}
	
	public boolean isValidating () {
		return factory.isValidating ();
	}
	
	private boolean isCell (String tagName) {
		return tagName.startsWith (LangUtils.H_) || tagName.startsWith(LangUtils.C_);
	}
	
	private boolean isLine (String tagName) {
		return tagName.equals (getTag (LangUtils.HS)) || tagName.equals(getTag (LangUtils.R));
	}
	
	private String getTag (String tagName) {
		String name = tags.get(tagName);
		if (name != null) {
			return name;
		}
		return tagName;
	}
	public boolean isOoxml() {
		return ooxml;
	}
	public void setOoxml(boolean ooxml) {
		this.ooxml = ooxml;
	}
	public Map<String, String> getTags() {
		return tags;
	}
	public void setTags(Map<String, String> tags) {
		this.tags = tags;
	}
	public Charset getCharset() {
		return charset;
	}
	public void setCharset(Charset charset) {
		this.charset = charset;
	}
	public LineCell getLineCell() {
		return lineCell;
	}
	public void setLineCell(LineCell lineCell) {
		this.lineCell = lineCell;
	}
	public CellStyleRegistry getStyleRegistry() {
		return styleRegistry;
	}
	public void setStyleRegistry(CellStyleRegistry stylesRegistry) {
		this.styleRegistry = stylesRegistry;
	}
	public boolean isInstallCustomFormats() {
		return installCustomFormats;
	}
	public void setInstallCustomFormats(boolean installCustomFormats) {
		this.installCustomFormats = installCustomFormats;
	}
}